﻿/**!
 * AngularJS file upload shim for HTML5 FormData
 * @author  Danial  <danial.farid@gmail.com>
 * @version <%= pkg.version %>
 */
(function () {

    var hasFlash = function () {
        try {
            var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
            if (fo) return true;
        } catch (e) {
            if (navigator.mimeTypes["application/x-shockwave-flash"] != undefined) return true;
        }
        return false;
    }

    var patchXHR = function (fnName, newFn) {
        window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
    };

    if (window.XMLHttpRequest) {
        if (window.FormData && (!window.FileAPI || !FileAPI.forceLoad)) {
            // allow access to Angular XHR private field: https://github.com/angular/angular.js/issues/1934
            patchXHR("setRequestHeader", function (orig) {
                return function (header, value) {
                    if (header === '__setXHR_') {
                        var val = value(this);
                        // fix for angular < 1.2.0
                        if (val instanceof Function) {
                            val(this);
                        }
                    } else {
                        orig.apply(this, arguments);
                    }
                }
            });
        } else {
            function initializeUploadListener(xhr) {
                if (!xhr.__listeners) {
                    if (!xhr.upload) xhr.upload = {};
                    xhr.__listeners = [];
                    var origAddEventListener = xhr.upload.addEventListener;
                    xhr.upload.addEventListener = function (t, fn, b) {
                        xhr.__listeners[t] = fn;
                        origAddEventListener && origAddEventListener.apply(this, arguments);
                    };
                }
            }

            patchXHR("open", function (orig) {
                return function (m, url, b) {
                    initializeUploadListener(this);
                    this.__url = url;
                    orig.apply(this, [m, url, b]);
                }
            });

            patchXHR("getResponseHeader", function (orig) {
                return function (h) {
                    return this.__fileApiXHR ? this.__fileApiXHR.getResponseHeader(h) : orig.apply(this, [h]);
                };
            });

            patchXHR("getAllResponseHeaders", function (orig) {
                return function () {
                    return this.__fileApiXHR ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
                }
            });

            patchXHR("abort", function (orig) {
                return function () {
                    return this.__fileApiXHR ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
                }
            });

            patchXHR("setRequestHeader", function (orig) {
                return function (header, value) {
                    if (header === '__setXHR_') {
                        initializeUploadListener(this);
                        var val = value(this);
                        // fix for angular < 1.2.0
                        if (val instanceof Function) {
                            val(this);
                        }
                    } else {
                        this.__requestHeaders = this.__requestHeaders || {};
                        this.__requestHeaders[header] = value;
                        orig.apply(this, arguments);
                    }
                }
            });

            patchXHR("send", function (orig) {
                return function () {
                    var xhr = this;
                    if (arguments[0] && arguments[0].__isShim) {
                        var formData = arguments[0];
                        var config = {
                            url: xhr.__url,
                            complete: function (err, fileApiXHR) {
                                if (!err && xhr.__listeners['load'])
                                    xhr.__listeners['load']({ type: 'load', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true });
                                if (!err && xhr.__listeners['loadend'])
                                    xhr.__listeners['loadend']({ type: 'loadend', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true });
                                if (err === 'abort' && xhr.__listeners['abort'])
                                    xhr.__listeners['abort']({ type: 'abort', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true });
                                if (fileApiXHR.status !== undefined) Object.defineProperty(xhr, 'status', { get: function () { return fileApiXHR.status } });
                                if (fileApiXHR.statusText !== undefined) Object.defineProperty(xhr, 'statusText', { get: function () { return fileApiXHR.statusText } });
                                Object.defineProperty(xhr, 'readyState', { get: function () { return 4 } });
                                if (fileApiXHR.response !== undefined) Object.defineProperty(xhr, 'response', { get: function () { return fileApiXHR.response } });
                                Object.defineProperty(xhr, 'responseText', { get: function () { return fileApiXHR.responseText } });
                                xhr.__fileApiXHR = fileApiXHR;
                                xhr.onreadystatechange();
                            },
                            fileprogress: function (e) {
                                e.target = xhr;
                                xhr.__listeners['progress'] && xhr.__listeners['progress'](e);
                                xhr.__total = e.total;
                                xhr.__loaded = e.loaded;
                            },
                            headers: xhr.__requestHeaders
                        }
                        config.data = {};
                        config.files = {}
                        for (var i = 0; i < formData.data.length; i++) {
                            var item = formData.data[i];
                            if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
                                config.files[item.key] = item.val;
                            } else {
                                config.data[item.key] = item.val;
                            }
                        }

                        setTimeout(function () {
                            if (!hasFlash()) {
                                throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
                            }
                            xhr.__fileApiXHR = FileAPI.upload(config);
                        }, 1);
                    } else {
                        orig.apply(xhr, arguments);
                    }
                }
            });
        }
        window.XMLHttpRequest.__isShim = true;
    }

    if (!window.FormData || (window.FileAPI && FileAPI.forceLoad)) {
        var wrapFileApi = function (elem) {
            if (!hasFlash()) {
                throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
            }
            if (!elem.__isWrapped && (elem.getAttribute('ng-file-select') != null || elem.getAttribute('data-ng-file-select') != null)) {
                var wrap = document.createElement('div');
                wrap.innerHTML = '<div class="js-fileapi-wrapper" style="position:relative; overflow:hidden"></div>';
                wrap = wrap.firstChild;
                var parent = elem.parentNode;
                parent.insertBefore(wrap, elem);
                parent.removeChild(elem);
                wrap.appendChild(elem);
                elem.__isWrapped = true;
            }
        };
        var changeFnWrapper = function (fn) {
            return function (evt) {
                var files = FileAPI.getFiles(evt);
                //just a double check for #233
                for (var i = 0; i < files.length; i++) {
                    if (files[i].size === undefined) files[i].size = 0;
                    if (files[i].name === undefined) files[i].name = 'file';
                    if (files[i].type === undefined) files[i].type = 'undefined';
                }
                if (!evt.target) {
                    evt.target = {};
                }
                evt.target.files = files;
                evt.target.files.item = function (i) {
                    return evt.target.files[i] || null;
                }
                fn(evt);
            };
        };
        var isFileChange = function (elem, e) {
            return (e.toLowerCase() === 'change' || e.toLowerCase() === 'onchange') && elem.getAttribute('type') == 'file';
        }
        if (HTMLInputElement.prototype.addEventListener) {
            HTMLInputElement.prototype.addEventListener = (function (origAddEventListener) {
                return function (e, fn, b, d) {
                    if (isFileChange(this, e)) {
                        wrapFileApi(this);
                        origAddEventListener.apply(this, [e, changeFnWrapper(fn), b, d]);
                    } else {
                        origAddEventListener.apply(this, [e, fn, b, d]);
                    }
                }
            })(HTMLInputElement.prototype.addEventListener);
        }
        if (HTMLInputElement.prototype.attachEvent) {
            HTMLInputElement.prototype.attachEvent = (function (origAttachEvent) {
                return function (e, fn) {
                    if (isFileChange(this, e)) {
                        wrapFileApi(this);
                        origAttachEvent.apply(this, [e, changeFnWrapper(fn)]);
                    } else {
                        origAttachEvent.apply(this, [e, fn]);
                    }
                }
            })(HTMLInputElement.prototype.attachEvent);
        }

        window.FormData = FormData = function () {
            return {
                append: function (key, val, name) {
                    this.data.push({
                        key: key,
                        val: val,
                        name: name
                    });
                },
                data: [],
                __isShim: true
            };
        };

        (function () {
            //load FileAPI
            if (!window.FileAPI) {
                window.FileAPI = {};
            }
            if (!FileAPI.upload) {
                var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
                if (window.FileAPI.jsUrl) {
                    jsUrl = window.FileAPI.jsUrl;
                } else if (window.FileAPI.jsPath) {
                    basePath = window.FileAPI.jsPath;
                } else {
                    for (i = 0; i < allScripts.length; i++) {
                        src = allScripts[i].src;
                        index = src.indexOf('angular-file-upload-shim.js')
                        if (index == -1) {
                            index = src.indexOf('angular-file-upload-shim.min.js');
                        }
                        if (index > -1) {
                            basePath = src.substring(0, index);
                            break;
                        }
                    }
                }

                if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
                script.setAttribute('src', jsUrl || basePath + "FileAPI.min.js");
                document.getElementsByTagName('head')[0].appendChild(script);
                FileAPI.hasFlash = hasFlash();
            }
        })();
    }


    if (!window.FileReader) {
        window.FileReader = function () {
            var _this = this, loadStarted = false;
            this.listeners = {};
            this.addEventListener = function (type, fn) {
                _this.listeners[type] = _this.listeners[type] || [];
                _this.listeners[type].push(fn);
            };
            this.removeEventListener = function (type, fn) {
                _this.listeners[type] && _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
            };
            this.dispatchEvent = function (evt) {
                var list = _this.listeners[evt.type];
                if (list) {
                    for (var i = 0; i < list.length; i++) {
                        list[i].call(_this, evt);
                    }
                }
            };
            this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;

            function constructEvent(type, evt) {
                var e = { type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error };
                if (evt.result != null) e.target.result = evt.result;
                return e;
            };
            var listener = function (evt) {
                if (!loadStarted) {
                    loadStarted = true;
                    _this.onloadstart && this.onloadstart(constructEvent('loadstart', evt));
                }
                if (evt.type === 'load') {
                    _this.onloadend && _this.onloadend(constructEvent('loadend', evt));
                    var e = constructEvent('load', evt);
                    _this.onload && _this.onload(e);
                    _this.dispatchEvent(e);
                } else if (evt.type === 'progress') {
                    var e = constructEvent('progress', evt);
                    _this.onprogress && _this.onprogress(e);
                    _this.dispatchEvent(e);
                } else {
                    var e = constructEvent('error', evt);
                    _this.onerror && _this.onerror(e);
                    _this.dispatchEvent(e);
                }
            };
            this.readAsArrayBuffer = function (file) {
                FileAPI.readAsBinaryString(file, listener);
            }
            this.readAsBinaryString = function (file) {
                FileAPI.readAsBinaryString(file, listener);
            }
            this.readAsDataURL = function (file) {
                FileAPI.readAsDataURL(file, listener);
            }
            this.readAsText = function (file) {
                FileAPI.readAsText(file, listener);
            }
        }
    }

})();